package com.lewin.ringroad.net.message;

import com.lewin.commons.utils.ByteUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.DecoderException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.nio.ByteOrder;
import java.util.Arrays;

import static com.lewin.ringroad.net.constants.RaysunCmdEnum.valueOf;

/**
 * 雷森基础报文
 *
 * @author Jun
 * @since 1.0.0
 */
@Slf4j
@Data
public class RaysunMessage implements ByteBufHolder {
    //@formatter:off
    /** 链路地址 */
    private byte link;

    /** 协议版本号 */
    private byte ver;

    private long radarId;

    /** 操作类型 */
    private byte type;

    /** 对象标识 */
    private byte cmd;

    private ByteBuf payload;

    /** 校验码 */
    private byte bcc;
    //@formatter:on

    /**
     * 解析报文创建 {@link RaysunMessage}
     *
     * @param buf {@link ByteBuf}
     * @return 雷森基础报文
     */
    public static RaysunMessage parseFrom(ByteBuf buf) {
        RaysunMessage msg = new RaysunMessage();

        // 转义报文和校验码
        byte[] payloadWaitEscape = new byte[buf.readableBytes()];
        buf.readBytes(payloadWaitEscape);
        byte[] escaped = inboundEscape(payloadWaitEscape);

        // payload, crc
        try {
            msg.link = escaped[0];
            msg.ver = escaped[1];
            msg.radarId = ByteUtils.bytes2unsignedInt(Arrays.copyOfRange(escaped, 2, 6), ByteOrder.LITTLE_ENDIAN);
            msg.type = escaped[6];
            msg.cmd = escaped[7];
            msg.payload = Unpooled.wrappedBuffer(Arrays.copyOfRange(escaped, 8, escaped.length - 1));
            msg.bcc = escaped[escaped.length - 1];
        } catch (Exception e) {
            log.error("解析失败的报文: {}", ByteUtils.toHexString(payloadWaitEscape));
            throw e;
        }

        return msg;
    }

    /**
     * 上行消息转义,仅支持如下格式:
     * <pre>
     *     消息体 + bcc
     * </pre>
     *
     * @param data 待转义字节数组
     */
    public static byte[] inboundEscape(byte[] data) {
        if (data == null || data.length == 0) {
            return null;
        }

        //先计算出长度
        int len = 0;
        for (byte b : data) {
            if (b == 0x7d) {
                ++len;
            }
        }
        if (len == 0) {
            return data;
        }

        byte[] result = new byte[data.length - len];
        for (int i = 0, j = 0; i < data.length; i++, j++) {
            if (data[i] == 0x7d) {
                byte mark = data[++i];
                if (mark == 0x5e) {
                    result[j] = 0x7e;
                    continue;
                } else if (mark == 0x5d) {
                    result[j] = 0x7d;
                    continue;
                } else {
                    throw new DecoderException("异常的报文");
                }
            }

            result[j] = data[i];
        }

        return result;
    }

    /**
     * 下行消息转义，支持的消息格式如下:
     * <pre>
     *     消息体 | bcc
     * </pre>
     *
     * @param bodyAndBcc 下行消息
     * @return 转义后的数据
     */
    private static byte[] outboundEscape(byte[] bodyAndBcc) {
        int len = 0;
        for (byte b : bodyAndBcc) {
            if (b == 0x7e || b == 0x7d) {
                ++len;
            }
        }
        if (len == 0) {
            return bodyAndBcc;
        }

        byte[] escapeBytes = new byte[bodyAndBcc.length + len];
        for (int i = 0, j = 0; i < bodyAndBcc.length; i++, j++) {
            if (bodyAndBcc[i] == 0x7e) {
                escapeBytes[j] = 0x7d;
                escapeBytes[++j] = 0x5e;
                continue;
            }
            if (bodyAndBcc[i] == 0x7d) {
                escapeBytes[j] = 0x7d;
                escapeBytes[++j] = 0x5d;
                continue;
            }
            escapeBytes[j] = bodyAndBcc[i];
        }

        return escapeBytes;
    }

    public BaseMessage toBaseMessage() {
        var cmdEnum = valueOf(cmd);
        if (cmdEnum == null) {
            return null;
        }
        BaseMessage bm = null;
        switch (cmdEnum) {
            case pulseData -> bm = new PulseMessage().parse(payload);
            case realtimeSpeed -> bm = new RealtimeSpeedMessage().parse(payload);
            case queueInfo -> bm = new QueueLengthMessage().parse(payload);
            case passingState -> bm = new PassingStatusMessage().parse(payload);
            case averageSpeed -> bm = new AverageSpeedMessage().parse(payload);
            case timeShare -> bm = new TimeShareMessage().parse(payload);
            case eventCheck -> bm = new EventCheckMessage().parse(payload);
            case coilStatistics -> bm = new CoilStatisticsMessage().parse(payload);
            case queueStatistics -> bm = new QueueStatisticMessage().parse(payload);
            case zoneStatistics -> bm = new ZoneStatisticMessage().parse(payload);
        }
        if (bm != null) {
            bm.setBizId(String.valueOf(radarId));
        }
        return bm;
    }

    @Override
    public ByteBuf content() {
        return ByteBufUtil.ensureAccessible(payload);
    }

    @Override
    public ByteBufHolder copy() {
        return replace(content().copy());
    }

    @Override
    public ByteBufHolder duplicate() {
        return replace(content().duplicate());
    }

    @Override
    public ByteBufHolder retainedDuplicate() {
        return replace(content().retainedDuplicate());
    }

    @Override
    public ByteBufHolder replace(ByteBuf content) {
        var msg = new RaysunMessage();
        msg.payload = content;
        msg.link = this.link;
        msg.ver = this.ver;
        msg.radarId = this.radarId;
        msg.type = this.type;
        msg.cmd = this.cmd;
        msg.bcc = this.bcc;

        return msg;
    }

    @Override
    public int refCnt() {
        return content().refCnt();
    }

    @Override
    public ByteBufHolder retain() {
        content().retain();
        return this;
    }

    @Override
    public ByteBufHolder retain(int increment) {
        content().retain(increment);
        return this;
    }

    @Override
    public ByteBufHolder touch() {
        content().touch();
        return this;
    }

    @Override
    public ByteBufHolder touch(Object hint) {
        content().touch(hint);
        return this;
    }

    @Override
    public boolean release() {
        return content().release();
    }

    @Override
    public boolean release(int decrement) {
        return content().release(decrement);
    }

    @Override
    public String toString() {
        int len = payload.readableBytes();
        byte[] bytes = new byte[len];
        payload.getBytes(0, bytes);

        return "RaysunMessage{" +
                "link=" + ByteUtils.toHexString(link) +
                ", ver=" + ByteUtils.toHexString(ver) +
                ", radarId=" + radarId +
                ", type=" + ByteUtils.toHexString(type) +
                ", cmd=" + ByteUtils.toHexString(cmd) +
                ", payload=" + ByteUtils.toHexString(bytes) +
                ", bcc=" + ByteUtils.toHexString(bcc) +
                '}';
    }
}
